package common.config.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import common.config.exception.CustomException;
import common.config.http.HttpStatusEnum;
import common.config.jwt.JwtUtil;
import common.config.rocketmq.MqTaskEnum;
import common.config.rocketmq.producer.SystemLogMqProducer;
import common.model.dto.LoginToken;
import common.model.po.SysLog;
import common.model.request.RegisterRequest;
import common.model.response.base.Result;
import common.util.HttpRequestUtil;
import common.util.IpUtils;
import common.util.SpecialUrlUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Optional;

/**
 * 通用系统日志切面
 *
 * @author 米泽鹏
 * @since 2022-02-26
 */
@Aspect
@Configuration
@Slf4j
public class CommonSystemLogAspect {

	@Value("${spring.application.name}")
	private String appName;
	@Resource
	private SystemLogMqProducer systemLogMqProducer;

	@Pointcut("execution(* *.controller..*(..))")
	public void systemLog() {
	}

	@Around(value = "systemLog()")
	public Object doAroundCommon(ProceedingJoinPoint joinPoint) throws Throwable {
		Object proceed;
		SysLog sysLog = new SysLog();
		HttpServletRequest request = HttpRequestUtil.getRequest();
		String token = JwtUtil.getTokenStringFromRequest(request);
		// 记录执行耗时
		long startTime = System.currentTimeMillis();
		try {
			// 获取方法名
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Method method = signature.getMethod();
			String methodName = method.getName();
			sysLog.setMethodName(methodName);
			// 获取knife4j注解上的方法描述
			ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
			if (apiOperation != null && StrUtil.isNotBlank(apiOperation.value())) {
				sysLog.setMethodDescription(apiOperation.value());
			}
			// 非网络请求环境不记录url和ip
			String url = null;
			if (request != null) {
				// 获取url
				url = request.getRequestURI().replace(request.getContextPath(), "");
				sysLog.setRequestUrl(url);
				// 获取ip地址
				String ip = IpUtils.getIpAddress(request);
				sysLog.setIp(ip);
			}
			// 获取operator：尝试从token获取用户名，如果获取不到，则不记录operator
			if (StrUtil.isNotBlank(token)) {
				LoginToken loginToken = JwtUtil.parseLoginToken(token);
				if (loginToken == null || StrUtil.isBlank(loginToken.getUsername())) {
					throw new CustomException("无法获取当前用户信息");
				}
				sysLog.setOperator(loginToken.getUsername());
			}
			Object[] args = joinPoint.getArgs();
			// 执行方法
			proceed = joinPoint.proceed(args);
			int statusCode;
			if (proceed instanceof Result) {
				Result<?> result = (Result<?>) proceed;
				statusCode = result.getCode();
			} else {
				statusCode = Optional.ofNullable(HttpStatusEnum.getStatus(request)).orElse(HttpStatusEnum.SUCCESS).getCode();
			}
			long endTime = System.currentTimeMillis();
			long totalTime = endTime - startTime;
			log.info("**********   Url: {}, Start: {}, End: {}, Total: {}ms, Code: {}   **********", url, DateUtil.format(new Date(startTime), "yyyy-MM-dd HH:mm:ss.SSS"), DateUtil.format(new Date(endTime), "yyyy-MM-dd HH:mm:ss.SSS"), totalTime, statusCode);
			sysLog.setExecutionTime(totalTime);
			sysLog.setExecutionDate(new Date());
			sysLog.setStatusCode(statusCode);
			if (SpecialUrlUtil.REGISTER.equals(url)) {
				sysLog.setMessage(HttpStatusEnum.SUCCESS.getMessage() + "，用户名：" + ((RegisterRequest) args[0]).getUsername());
			} else {
				sysLog.setMessage(HttpStatusEnum.SUCCESS.getMessage());
			}
			// RocketMQ单向消息：记录系统日志
			Message msg = new Message(MqTaskEnum.SYSTEM_LOG.getTopic(), appName, JSONUtil.toJsonStr(sysLog).getBytes(RemotingHelper.DEFAULT_CHARSET));
			systemLogMqProducer.sendOneway(msg);
			log.info("已发送RocketMQ单向消息：系统日志，来自服务：" + appName);
		} catch (Exception e) {
			sysLog.setExecutionTime(System.currentTimeMillis() - startTime);
			sysLog.setExecutionDate(new Date());
			sysLog.setStatusCode(Optional.ofNullable(HttpStatusEnum.getStatus(request)).orElse(HttpStatusEnum.INTERNAL_SERVER_ERROR).getCode());
			// e.getMessage()有时候返回的是null，这里用ExceptionUtil.getRootCauseMessage(e)代替
			sysLog.setMessage(ExceptionUtil.getRootCauseMessage(e));
			// RocketMQ单向消息：记录系统日志
			Message msg = new Message(MqTaskEnum.SYSTEM_LOG.getTopic(), appName, JSONUtil.toJsonStr(sysLog).getBytes(RemotingHelper.DEFAULT_CHARSET));
			systemLogMqProducer.sendOneway(msg);
			log.info("已发送RocketMQ单向消息：系统日志，来自服务：" + appName);
			log.error(ExceptionUtil.getRootCauseMessage(e));
			throw e;
		}
		return proceed;
	}

}
